-
-
Notifications
You must be signed in to change notification settings - Fork 343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New faster Queue with capacity >= 0 #473
Conversation
Codecov Report
@@ Coverage Diff @@
## master #473 +/- ##
==========================================
- Coverage 99.27% 99.27% -0.01%
==========================================
Files 89 89
Lines 10405 10468 +63
Branches 721 727 +6
==========================================
+ Hits 10330 10392 +62
Misses 58 58
- Partials 17 18 +1
Continue to review full report at Codecov.
|
based on ideas and code from python-trio#272
I'm quite hesitant about adding a It'd be good to benchmark just to check, and to document what exactly a 0-size buffer actually means for those who have not been initiated into the ways of CSP. |
Benchmarking code: https://gist.github.com/sorcio/71cbad90335808544c7ea4edbdc9264f With patch:
Without patch (
I was thinking of writing more microbenchmarks but I stopped here because I'm not sure how to exercise the right parts and because I don't understand the huge slowness of current I can update the patch to remove The thing I don't understand though is whether there is some way to achieve the same result without |
Same microbenchmarks as before run on updated master vs updated PR. Now formatted in a fancy table because hey fancy tables. Times in seconds, 10 rounds each. Not comparable with previous benchmark because this ran on a different machine, but also because Trio got generally slower since I last ran the benchmarks.
|
@njsmith what do you think of this? We can leave this with min size 1, so no documentation change is needed and if it's alright with you it can go in. Otherwise I'm not entirely sure what kind of doc update would be good :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looking pretty good! Two small requests below. Also:
Re: docs: I think it'd be good to add a few sentences somewhere explaining that a Queue(0)
means that put
blocks until get
is called and vice-versa, like a CSP channel. It doesn't have to be long or complicated, but for those who haven't been initiated into the mysteries of CSP, I think it's good to at least give the basic idea and give them a pointer to find out more.
And one last thing: can you add a towncrier entry? https://trio.readthedocs.io/en/latest/contributing.html#pull-request-release-notes
The support for Queue(0)
, and that queue is faster, are both worth mentioning in the release notes.
trio/_sync.py
Outdated
self._put_semaphore = Semaphore(capacity, max_value=capacity) | ||
self._get_semaphore = Semaphore(0, max_value=capacity) | ||
# {task: abort func} | ||
self._get_wait = OrderedDict() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why this has the abort fn in it. It looks like all the abort fn does is remove the task from self._get_wait
, and the only place we use it is in put
, which calls it... immediately after removing the task from self._get_wait
, so it's a no-op. And then we have a try
/except
in the get
abort fn just to work around this?
Can we make _get_wait
a mapping {task: None}
, i.e. basically just an ordered set? Or is there a subtlety I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On further thought: I bet this is left-over from my multi_get
hack, huh. I think it's unnecessary here now though...
@@ -512,6 +510,29 @@ | |||
assert (await q.get()) == 2 | |||
|
|||
|
|||
async def test_Queue_unbuffered(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a bit more checking of unbuffered Queue
, I'd suggest adding a QueueLock1(0)
to the lock_factories
and lock_factory_names
lists in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this works. Attempting acquire the lock (i.e. await queue.put()
) will block indefinitely. I definitely like idea to test Queue(0)
as used for explicit synchronization though, so I may add a test that relies on rendezvous behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempting acquire the lock (i.e.
await queue.put()
) will block indefinitely
Oh duh, of course you're right.
|
Having a zero-size queue can sound weird unless you realize that the queue both queues data and task scheduling - maybe you should edit the doc to explain why 0-capacity queues would be useful? (to me it doesn't really feel like a queue at that point, more of an event notifier). |
@Fuyukai hmm... I see your point. Where I come from, I see a queue as a FIFO channel: whatever you Trio docs say "You can use Queue objects to safely pass objects between tasks", so it's pretty neutral w.r.t. buffers. If you think that we should explain why 0-capacity queues would be useful, I guess that means that from some point of view one user might assume that queues are just some kind of data structure (like e.g. |
It's still a queue in the sense that you can call |
Based on ideas and code by @njsmith (#272). Made a PR out of it to discuss API.